package com.dw.library.view;

import static android.widget.LinearLayout.HORIZONTAL;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.dw.library.R;

/**
 * 
 */
public abstract class AbsViewPagerIndicator extends View implements OnPageChangeListener {
  private static final String TAG = "ViewPagerIndicator";

  private static final int INVALID_POINTER = -1;

  public static final int ORIENTATION_HORIZONTAL = 0;
  public static final int ORIENTATION_VERTICAL = 1;

  public static final int GRAVITY_CENTER = 0;
  public static final int GRAVITY_LEFT = 1;
  public static final int GRAVITY_RIGHT = 2;

  protected ViewPager mViewPager;
  private ViewPager.OnPageChangeListener mListener;

  private int mOrientation;
  private boolean mSnap = false;
  private float mInternalPadding;
  private float mItemW;
  private float mItemH;
  private int mGravity;
  private boolean clickSnapEnable = true;

  private int mTouchSlop;
  private float mLastMotionX = -1;
  private float mLastMotionY = -1;
  private int mActivePointerId = INVALID_POINTER;
  private boolean mIsDragging;
  private int mScrollState;

  protected int mCurrentPage;
  protected float mPositionOffset;
  private int mSnapPage;

  private boolean indicatorSet = false;

  public AbsViewPagerIndicator(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    Resources res = getResources();
    final float defInternalPadding = res.getDimension(R.dimen.def_vpi_internal_padding);
    final float defItemW = res.getDimension(R.dimen.def_vpi_item_width);
    final float defItemH = res.getDimension(R.dimen.def_vpi_item_height);
    Log.i(TAG, "def_item_w->" + defItemW + ",def_item_h->" + defItemH);
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AbsViewPagerIndicator);
    mOrientation = array.getInt(R.styleable.AbsViewPagerIndicator_vpiOrientation, ORIENTATION_HORIZONTAL);
    mSnap = array.getBoolean(R.styleable.AbsViewPagerIndicator_vpiSnap, false);
    mInternalPadding = array.getDimension(R.styleable.AbsViewPagerIndicator_vpiInternalPadding, defInternalPadding);
    mItemW = array.getDimension(R.styleable.AbsViewPagerIndicator_vpiItemWidth, (int) defItemW);
    mItemH = array.getDimension(R.styleable.AbsViewPagerIndicator_vpiItemHeight, defItemH);
    mGravity = array.getInt(R.styleable.AbsViewPagerIndicator_vpiGravity, GRAVITY_CENTER);
    array.recycle();
  }

  public AbsViewPagerIndicator(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public AbsViewPagerIndicator(Context context) {
    this(context, null);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    PagerAdapter adapter = null;
    try {
      adapter = mViewPager.getAdapter();
      if (null == adapter) {
        return;
      }
    } catch (Exception e) {
      return;
    }
    final int count = adapter.getCount();
    final int width = this.getWidth();
    final int height = this.getHeight();
    final int pLeft = this.getPaddingLeft();
    final int pTop = this.getPaddingTop();
    final int pRight = this.getPaddingRight();
    final int pBottom = this.getPaddingBottom();
    float top = 0;
    float left = 0;

    switch (mGravity) {
      case GRAVITY_CENTER:
        if (mOrientation == ORIENTATION_HORIZONTAL) {
          left = (width - (mItemW + mInternalPadding) * count + mInternalPadding - pLeft - pRight - 1) / 2f + pLeft;
          top = (height - pTop - pBottom - mItemH) / 2f + pTop;
        } else {
          left = (width - mItemW - pLeft - pRight) / 2f + pLeft;
          top = (height - (mItemH + mInternalPadding) * count + mInternalPadding - pTop - pBottom - 1) / 2f + pTop;
        }
        break;
      case GRAVITY_LEFT:
        left = pLeft;
        if (mOrientation == ORIENTATION_HORIZONTAL) {
          top = (height - pTop - pBottom - mItemH) / 2f + pTop;
        } else {
          top = (height - (mItemH + mInternalPadding) * count + mInternalPadding - pTop - pBottom - 1) / 2f + pTop;
        }
        break;
      case GRAVITY_RIGHT:
        if (mOrientation == ORIENTATION_HORIZONTAL) {
          left = width - (mItemW + mInternalPadding) * count + mInternalPadding - pRight - 1;
          top = (height - pTop - pBottom - mItemH) / 2f + pTop;
        } else {
          left = width - mItemW - pRight;
          top = (height - (mItemH + mInternalPadding) * count + mInternalPadding - pTop - pBottom - 1) / 2f + pTop;
        }
        break;
      default:
        break;
    }
    float l = 0;
    float t = 0;
    // 画指示器背景
    for (int i = 0; i < count; i++) {
      if (mOrientation == ORIENTATION_HORIZONTAL) {
        l = left + (mItemW + mInternalPadding) * i;
        t = top;
      } else {
        l = left;
        t = top + (mItemH + mInternalPadding) * i;
      }
      drawBackground(canvas, l, t, l + mItemW, t + mItemH);
    }

    // 画指示器
    if (mOrientation == ORIENTATION_HORIZONTAL) {
      if (mSnap) {
        l = left + (mItemW + mInternalPadding) * mSnapPage;
      } else {
        l = left + (mItemW + mInternalPadding) * mCurrentPage + (mItemW + mInternalPadding) * mPositionOffset;
      }
      t = top;
    } else {
      if (mSnap) {
        t = top + (mItemH + mInternalPadding) * mSnapPage;
      } else {
        t = top + (mItemH + mInternalPadding) * mCurrentPage + (mItemH + mInternalPadding) * mPositionOffset;
      }
      l = left;
    }
    drawIndicator(canvas, l, t, l + mItemW, t + mItemH);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == HORIZONTAL) {
      setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
    } else {
      setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
    }
  }

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (super.onTouchEvent(ev)) {
      return true;
    }
    if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
      return false;
    }
    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        mLastMotionX = ev.getX();
        mLastMotionY = ev.getY();
        break;

      case MotionEvent.ACTION_MOVE:
        final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);

        float x = MotionEventCompat.getX(ev, activePointerIndex);
        float y = MotionEventCompat.getY(ev, activePointerIndex);
        float delta = 0;
        if (mOrientation == ORIENTATION_HORIZONTAL) {
          delta = x - mLastMotionX;
        } else if (mOrientation == ORIENTATION_VERTICAL) {
          delta = y - mLastMotionY;
        }
        if (!mIsDragging) {
          if (Math.abs(delta) > mTouchSlop) {
            mIsDragging = true;
          }
        }
        if (mIsDragging) {
          mLastMotionX = x;
          mLastMotionY = y;
          if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
            mViewPager.fakeDragBy(delta);
          }
        }
        break;

      case MotionEvent.ACTION_CANCEL:

      case MotionEvent.ACTION_UP:
        if (!mIsDragging && clickSnapEnable) {
          final int count = mViewPager.getAdapter().getCount();
          if (mOrientation == ORIENTATION_HORIZONTAL) {
            final int width = getWidth();
            final float w = (mItemW + mInternalPadding) * count - mInternalPadding;
            if ((mCurrentPage > 0) && (ev.getX() < (width-w)/2)) {
              if (action != MotionEvent.ACTION_CANCEL) {
                mViewPager.setCurrentItem(mCurrentPage - 1);
              }
              return true;
            } else if ((mCurrentPage < count - 1) && (ev.getX() > (width+w)/2)) {
              if (action != MotionEvent.ACTION_CANCEL) {
                mViewPager.setCurrentItem(mCurrentPage + 1);
              }
              return true;
            }
          }else if(mOrientation == ORIENTATION_VERTICAL){
            final int height = getHeight();
            final float w = (mItemH + mInternalPadding) * count - mInternalPadding;
            if ((mCurrentPage > 0) && (ev.getY() < (height-w)/2)) {
              if (action != MotionEvent.ACTION_CANCEL) {
                mViewPager.setCurrentItem(mCurrentPage - 1);
              }
              return true;
            } else if ((mCurrentPage < count - 1) && (ev.getY() > (height+w)/2)) {
              if (action != MotionEvent.ACTION_CANCEL) {
                mViewPager.setCurrentItem(mCurrentPage + 1);
              }
              return true;
            }
          }
        }

        mIsDragging = false;
        mActivePointerId = INVALID_POINTER;
        if (mViewPager.isFakeDragging()) {
          mViewPager.endFakeDrag();
        }
        break;

      case MotionEventCompat.ACTION_POINTER_DOWN:
        final int index = MotionEventCompat.getActionIndex(ev);
        mLastMotionX = MotionEventCompat.getX(ev, index);
        mLastMotionY = MotionEventCompat.getY(ev, index);
        mActivePointerId = MotionEventCompat.getPointerId(ev, index);
        break;

      case MotionEventCompat.ACTION_POINTER_UP:
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == mActivePointerId) {
          final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
          mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
        mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        mLastMotionY = MotionEventCompat.getY(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        break;
    }
    return true;
  }

  @Override
  public void onRestoreInstanceState(Parcelable state) {
    SavedState savedState = (SavedState) state;
    super.onRestoreInstanceState(savedState.getSuperState());
    mCurrentPage = savedState.currentPage;
    mSnapPage = savedState.currentPage;
    requestLayout();
  }

  @Override
  public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    SavedState savedState = new SavedState(superState);
    savedState.currentPage = mCurrentPage;
    return savedState;
  }

  @Override
  public void onPageScrollStateChanged(int state) {
    mScrollState = state;

    if (mListener != null) {
      mListener.onPageScrollStateChanged(state);
    }
  }

  @Override
  public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    mCurrentPage = position;
    mPositionOffset = positionOffset;
    invalidate();

    if (mListener != null) {
      mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
    }
  }

  @Override
  public void onPageSelected(int position) {
    if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
      mCurrentPage = position;
      mSnapPage = position;
      invalidate();
    }
    if (mListener != null) {
      mListener.onPageSelected(position);
    }
  }

  /**
   * 
   * 
   * @param orientation
   *          the value is {@link #ORIENTATION_HORIZONTAL} or
   *          {@link #ORIENTATION_VERTICAL}
   */
  public void setOrientation(int orientation) {
    switch (orientation) {
      case ORIENTATION_HORIZONTAL:
      case ORIENTATION_VERTICAL:
        mOrientation = orientation;
        requestLayout();
        break;

      default:
        throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
    }
  }

  public boolean isSnap() {
    return mSnap;
  }

  public void setSnap(boolean snap) {
    this.mSnap = snap;
  }

  public float getInternalPadding() {
    return mInternalPadding;
  }

  public void setInternalPadding(float internalPadding) {
    this.mInternalPadding = internalPadding;
  }

  public float getItemW() {
    return mItemW;
  }

  public void setItemW(float itemW) {
    this.mItemW = itemW;
  }

  public float getItemH() {
    return mItemH;
  }

  public void setItemH(float itemH) {
    this.mItemH = itemH;
  }

  /**
   * 
   * @param gravity
   *          the value is {@link #GRAVITY_CENTER_HORIZONTAL}
   *          {@link #GRAVITY_LEFT} {@link #GRAVITY_RIGHT}
   */
  public void setGravity(int gravity) {
    if (this.mGravity == gravity) {
      return;
    }
    this.mGravity = gravity;
    invalidate();
  }

  public void setViewPager(ViewPager view) {
    if (mViewPager == view) {
      return;
    }
    if (mViewPager != null) {
      mViewPager.setOnPageChangeListener(null);
    }
    if (view.getAdapter() == null) {
      throw new IllegalStateException("ViewPager does not have adapter instance.");
    }
    mViewPager = view;
    if (!indicatorSet) {
      mViewPager.setOnPageChangeListener(this);
    }
    invalidate();
  }

  public void setViewPager(ViewPager view, int initialPosition) {
    setViewPager(view);
    setCurrentItem(initialPosition);
  }

  public void setCurrentItem(int item) {
    if (mViewPager == null) {
      throw new IllegalStateException("ViewPager has not been bound.");
    }
    mViewPager.setCurrentItem(item);
    mCurrentPage = item;
    invalidate();
  }

  public boolean isClickSnapEnable() {
    return clickSnapEnable;
  }

  public void setClickSnapEnable(boolean clickSnapEnable) {
    this.clickSnapEnable = clickSnapEnable;
  }

  public boolean isIndicatorSet() {
    return indicatorSet;
  }

  public void setIndicatorSet(boolean indicatorSet) {
    this.indicatorSet = indicatorSet;
  }

  public void notifyDataSetChanged() {
    invalidate();
  }

  public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
    mListener = listener;
  }

  private int measureLong(int measureSpec) {
    int result;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
      // We were told how big to be
      result = specSize;
    } else {
      // Calculate the width according the views count
      final int count = mViewPager.getAdapter().getCount();
      result = (int) (getPaddingLeft() + getPaddingRight() + (count * mItemW) + (count - 1) * mInternalPadding + 1);
      // Respect AT_MOST value if that was what is called for by measureSpec
      if (specMode == MeasureSpec.AT_MOST) {
        result = Math.min(result, specSize);
      }
    }
    return result;
  }

  /**
   * Determines the height of this view
   * 
   * @param measureSpec
   *          A measureSpec packed into an int
   * @return The height of the view, honoring constraints from measureSpec
   */
  private int measureShort(int measureSpec) {
    int result;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    if (specMode == MeasureSpec.EXACTLY) {
      // We were told how big to be
      result = specSize;
    } else {
      // Measure the height
      result = (int) (2 * mItemH + getPaddingTop() + getPaddingBottom() + 1);
      // Respect AT_MOST value if that was what is called for by measureSpec
      if (specMode == MeasureSpec.AT_MOST) {
        result = Math.min(result, specSize);
      }
    }
    return result;
  }

  static class SavedState extends BaseSavedState {
    int currentPage;

    public SavedState(Parcelable superState) {
      super(superState);
    }

    private SavedState(Parcel in) {
      super(in);
      currentPage = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      super.writeToParcel(dest, flags);
      dest.writeInt(currentPage);
    }

    @SuppressWarnings("UnusedDeclaration")
    public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
      @Override
      public SavedState createFromParcel(Parcel in) {
        return new SavedState(in);
      }

      @Override
      public SavedState[] newArray(int size) {
        return new SavedState[size];
      }
    };
  }

  /**
   * draw indicator's background
   * 
   * @param canvas
   * @param left
   * @param top
   * @param right
   * @param bottom
   */
  protected abstract void drawBackground(Canvas canvas, float left, float top, float right, float bottom);

  /**
   * draw indicator
   * 
   * @param canvas
   * @param left
   * @param top
   * @param right
   * @param bottom
   */
  protected abstract void drawIndicator(Canvas canvas, float left, float top, float right, float bottom);
}
